﻿using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using System.Diagnostics;
using PowersourceUIToolkit;
using System.ComponentModel.Composition;
using Microsoft.TeamFoundation.Client;
using System.IO;
using System.Windows.Threading;

namespace UCL.Powersource.TFS
{

    public class RemoteBuilderQueue : PropertyChangedInitializedLifeCycle, IRemoteBuilderQueue
    {

        public event EventHandler<RemoteBuilderQueueEventArgs> RemoteBuilderQueueUpdated;

        private Queue<RemoteBuilderQueueItem> _WaitingBuilds;
        //public IReadOnlyList<IRemoteBuilderQueueItem> FinishedBuilds { get { return _FinishedBuilds; } }
        private List<RemoteBuilderQueueItem> _FinishedBuilds;
        private ILog _Log;
        public ILog Log
        {
            get { return _Log ?? (_Log = LogManager.GetLog(this.GetType())); }
        }
        private RemoteBuilderAdaptor RemoteBuilderAdaptor;

        private Thread QueueThread;
        private bool FinishQueueThread = false;
        private EventWaitHandle QueueThreadHasWork;
        //private bool TerminateAllBuilds_Requested;
        private bool _TerminateCurrentBuild_Requested;
        private bool TerminateCurrentBuild_Requested
        {
            get { return _TerminateCurrentBuild_Requested; }
            set { _TerminateCurrentBuild_Requested = value; }
        }

        #region CurrentAction

        /// <summary>
        /// Field storage for property CurrentAction
        /// </summary>
        private string _CurrentAction = default(string);

        /// <summary>
        /// Property CurrentAction. TODODOC: Insert appropriate summary here
        /// </summary>
        public string CurrentAction
        {
            get { return _CurrentAction; }
            set { if (_CurrentAction == value) return; _CurrentAction = value; CurrentAction_RecalculateDependencies(); }
        }

        private void CurrentAction_RecalculateDependencies()
        {
            //Recalculate all properties here

            //NotifyOfPropertyChange is usually placed as last statement - first change all dependencies and then fire property change
            InvokeOnUIDispatcher(() => { NotifyOfPropertyChange(() => CurrentAction); });
        }

        //private void CurrentAction_Recalculate()
        //{
        //    _CurrentAction = default(string);
        //    //Insert your code here

        //    CurrentAction_RecalculateDependencies();
        //}

        #endregion


        #region IsCalculating

        /////// <summary>
        /////// Field storage for property IsCalculating
        /////// </summary>
        ////private bool _IsCalculating = default(bool);

        /// <summary>
        /// Property IsCalculating. TODODOC: Insert appropriate summary here
        /// </summary>
        public bool IsCalculating
        {
            get { return _WaitingBuilds != null && _WaitingBuilds.Count > 0; }
            //set { if (_IsCalculating == value) return; _IsCalculating = value; IsCalculating_RecalculateDependencies(); }
        }

        private void IsCalculating_RecalculateDependencies()
        {
            //Recalculate all properties here

            //NotifyOfPropertyChange is usually placed as last statement - first change all dependencies and then fire property change
            NotifyOfPropertyChange(() => IsCalculating);
        }

        private void IsCalculating_Recalculate()
        {
            IsCalculating_RecalculateDependencies();
        }

        private void IsCalculating_Recalculate_NonUICall()
        {
            try
            {
                InvokeOnUIDispatcher(IsCalculating_Recalculate);
            }
            catch (Exception exc)
            {
                if (Log != null) Log.Error(exc, "IsCalculating_Recalculate_NonUICall not successfull while trying to Invoke on UI thread");

            }
        }


        #endregion


        #region WaitingBuildsCount

        /// <summary>
        /// Field storage for property WaitingBuildsCount
        /// </summary>
        private int _WaitingBuildsCount = default(int);

        /// <summary>
        /// Property WaitingBuildsCount. TODODOC: Insert appropriate summary here
        /// </summary>
        public int WaitingBuildsCount
        {
            get
            {
                return _WaitingBuildsCount;

            }
            //set { if (_WaitingBuildsCount == value) return; _WaitingBuildsCount = value; WaitingBuildsCount_RecalculateDependencies(); }
        }

        private void WaitingBuildsCount_RecalculateDependencies()
        {
            //Recalculate all properties here

            //NotifyOfPropertyChange is usually placed as last statement - first change all dependencies and then fire property change
            NotifyOfPropertyChange(() => WaitingBuildsCount);
        }

        private void WaitingBuildsCount_Recalculate()
        {
            var cnt = 0;
            cnt = _WaitingBuilds == null ? 0 : _WaitingBuilds.Count;
            _WaitingBuildsCount = cnt;
            WaitingBuildsCount_RecalculateDependencies();
        }

        private void WaitingBuildsCount_Recalculate_NonUICall()
        {
            InvokeOnUIDispatcher(WaitingBuildsCount_Recalculate);
        }

        #endregion


        #region FinishedBuildsCount

        /// <summary>
        /// Field storage for property FinishedBuildsCount
        /// </summary>
        private int _FinishedBuildsCount = default(int);

        /// <summary>
        /// Property FinishedBuildsCount. TODODOC: Insert appropriate summary here
        /// </summary>
        public int FinishedBuildsCount
        {
            get { return _FinishedBuildsCount; }
            //set { if (_FinishedBuildsCount == value) return; _FinishedBuildsCount = value; FinishedBuildsCount_RecalculateDependencies(); }
        }

        private void FinishedBuildsCount_RecalculateDependencies()
        {
            NotifyOfPropertyChange(() => FinishedBuildsCount);
        }

        private void FinishedBuildsCount_Recalculate()
        {
            var cnt = 0;
            cnt = _FinishedBuilds == null ? 0 : _FinishedBuilds.Count;
            _FinishedBuildsCount = cnt;
            FinishedBuildsCount_RecalculateDependencies();
        }

        private void FinishedBuildsCount_Recalculate_NonUICall()
        {
            InvokeOnUIDispatcher(FinishedBuildsCount_Recalculate);
        }

        #endregion

        public bool IsCurrentThreadUIThread(Thread thread)
        {
            return UIDispatcher.Thread == thread;
        }

        public Dispatcher UIDispatcher { get; private set; }

        private void InvokeOnUIDispatcher(System.Action callback)
        {
            UIDispatcher.Invoke(callback);
        }


        private void InvokeOnUIDispatcher(Delegate action, params object[] ea)
        {
            UIDispatcher.Invoke(action, ea);
        }


        public RemoteBuilderQueue()
        {
            //Initialize();
        }

        protected override bool OnInitializing()
        {
            UIDispatcher = Dispatcher.CurrentDispatcher;
            _WaitingBuilds = new Queue<RemoteBuilderQueueItem>();
            _FinishedBuilds = new List<RemoteBuilderQueueItem>();

            RemoteBuilderAdaptor = new RemoteBuilderAdaptor(this);//IoC.Get<RemoteBuilderAdaptor>();
            QueueThreadHasWork = new EventWaitHandle(false, EventResetMode.AutoReset);
            QueueThread = new Thread(new ThreadStart(QueueWorker));
            QueueThread.Name = this.GetType().Name;
            QueueThread.Start();
            return true;
        }



        private void QueueWorker()
        {
            RemoteBuilderQueueItem buildingItem = null;
            try
            {

                while (!FinishQueueThread)
                {
                    int cnt = 0;
                    lock (this)
                    {
                        cnt = _WaitingBuilds.Count;
                    }
                    while (cnt > 0)
                    {
                        if (FinishQueueThread) return;
                        lock (this)
                        {
                            buildingItem = _WaitingBuilds.Peek();
                            if (buildingItem == null) break;
                        }
                        buildingItem.Activated();
                        CurrentAction = buildingItem.GetActionString();
                        RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Checking Build");
                        try
                        {
                            var terminatedNormally = false;
                            TerminateCurrentBuild_Requested = false;
                            //string waitingForBuild = ".";
                            DateTime startOfBuild = DateTime.Now;
                            var tpcc = buildingItem.Project.TeamProjectCollectionConfiguration;
                            var tp = buildingItem.Project;
                            var tf = buildingItem.TimeFrame;
                            string localBuildPath = AnalysisBridge.Conventions.GetLocalPathFor(tpcc, tp, tf);
                            if (!Directory.Exists(localBuildPath) || buildingItem.ForceRecalculate)
                            {
                                CurrentAction = buildingItem.GetActionString();
                                RemoteBuilderAdaptor.QueueNewBuild(buildingItem);
                                RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Building");
                                using (var r = RemoteBuilderAdaptor.GetQueuedBuildStatusEnumerator())
                                {
                                    while (r.MoveNext())
                                    {
                                        if (r.Current != null && r.Current.IsFinalQueuedBuildStatus)
                                        {
                                            //Normal termination is when after IsFinalQueuedBuildStatus there is no QueuedBuildStatus yielded
                                            terminatedNormally = !r.MoveNext();
                                            RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Build normally finished, running post-build actions (CodeIndex, Dissasembly)");
                                            buildingItem.Finish(r.Current.BuildDetail, r.Current.PollingException, terminatedNormally);
                                            CurrentAction = buildingItem.GetActionString();
                                            
                                            break;
                                        }
                                        if (TerminateCurrentBuild_Requested)
                                        {
                                            RemoteBuilderAdaptor.RequestTerminate(new ApplicationException("Terminate the build on demand"));
                                        }
                                        else
                                        {
                                            //var buildingFor = DateTime.Now.Subtract(startOfBuild);
                                            //if (waitingForBuild.Length > 10) waitingForBuild = ".";
                                            RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Building on server");
                                            Thread.Sleep(500);
                                        }
                                    }

                                    if (buildingItem.Status != QueuedItemStatus.Finished)
                                    {
                                        Log.Warn("Forcing build {0} to finish.", buildingItem.ToString());
                                        if (r.Current != null)
                                        {
                                            buildingItem.Finish(r.Current.BuildDetail, r.Current.PollingException, terminatedNormally);
                                            CurrentAction = buildingItem.GetActionString();
                                            RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Build forced to finish");
                                        }
                                        else
                                        {
                                            buildingItem.Finish(buildingItem.BuildDetail, null, terminatedNormally);
                                            CurrentAction = buildingItem.GetActionString();
                                            RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Build forced to finish, coroutine enumarator is null!");
                                        }

                                    }
                                    CurrentAction = buildingItem.GetActionString();
                                }
                            }
                            else
                            {
                                buildingItem.Finish(buildingItem.BuildDetail, null, true);
                                Log.Warn("Finishing build {0}. {1} exists and forcerecalculate=false", buildingItem.ToString(), localBuildPath);
                                RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Build skipped, folder exists");
                                CurrentAction = buildingItem.GetActionString();
                                Thread.Sleep(200);
                            }
                        }
                        catch (Exception exc)
                        {
                            if (buildingItem != null)
                            {
                                Log.Error(exc, "Exception while building. Forcing build to finish.");
                                RemoteBuilderAdaptor.RequestTerminate(exc);
                                buildingItem.Finish(buildingItem.BuildDetail, exc, false);
                                CurrentAction = buildingItem.GetActionString();
                                RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, exc, "Build forced to finished due to exception {0}", exc);
                            }
                        }
                        cnt = Dequeue_Internal(buildingItem);

                        RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Finished and Removed");

                    }
                    //RaiseRemoteBuilderQueueUpdated_WithoutLock(buildingItem, buildingItem.Status, null, "Build forced to finished due to exception {0}", exc);
                    IsCalculating_Recalculate_NonUICall();
                    Log.DebugMethod("Nothing to work on in {0}, QueueThread is invoking WaitOne.", this.ToString());
                    QueueThreadHasWork.WaitOne(); //Wait for a signal that there is some work to do
                }
            }
            catch (ThreadAbortException)
            {
                if (buildingItem != null)
                {
                    Log.Warn("{1} QueueThread abort. Abandoning bd {0}", buildingItem.ToString(), this.ToString());
                }
                IsCalculating_Recalculate_NonUICall();


            }
            catch (Exception exc)
            {
                Log.Error(exc, "Abnormal {0} QueueThread Termination. Building service may not work", this.ToString());
                IsCalculating_Recalculate_NonUICall();
            }
        }


        private void RaiseRemoteBuilderQueueUpdated_InUIThread(RemoteBuilderQueueEventArgs ea)
        {
            Log.Debug("RM queue update: {0}", ea.ToString());
            if (RemoteBuilderQueueUpdated != null)
            {
                RemoteBuilderQueueUpdated(this, ea);
            }
        }
        internal protected void RaiseRemoteBuilderQueueUpdated_WithoutLock(RemoteBuilderQueueItem item, QueuedItemStatus status, Exception exc, string format, params object[] args)
        {
            // We create EventArgs in Non UI thread and then Dispatch it to UI thread - therefore values will never change in UI thread;
            RemoteBuilderQueueEventArgs ea = new RemoteBuilderQueueEventArgs(item, item.Status, exc, this, format, args);
            InvokeOnUIDispatcher(new Action<RemoteBuilderQueueEventArgs>(RaiseRemoteBuilderQueueUpdated_InUIThread), ea);
        }

        public IRemoteBuilderQueueItem GetBuild(ITfsTeamProjectConfiguration project, ITimeFrame timeFrame, bool forceRecalculate)
        {
            if (!IsInitialized) Initialize();

            var item = RequestBuildItem(project, timeFrame, forceRecalculate);
            if (item.Status == QueuedItemStatus.Finished) return item;

            var buildCompleted = false;
            GetBuildAsync(project, timeFrame, forceRecalculate, () => { buildCompleted = true; });
            while (!buildCompleted)
            {
                Thread.Sleep(200);
            }
            return item;
        }


        public IRemoteBuilderQueueItem GetBuildAsync(ITfsTeamProjectConfiguration project, ITimeFrame timeFrame, bool forceRecalculate, System.Action OnCompletedCallback )
        {
            if (!IsInitialized) Initialize();
            Log.Debug("GetBuildAsync request: {0} - {1}", project.Label, timeFrame.Label);

            IRemoteBuilderQueueItem qi = RequestBuildItem(project, timeFrame, forceRecalculate);
            var isFinished = false;
            lock (this) //Synchronized check if the item haven't finished
            {
                isFinished = (qi.Status == QueuedItemStatus.Finished);
                if (!isFinished) //If not we attached listeners [Must be in critical section]
                {
                    //if (OnChange != null) qi.Changed += OnChange;

                    if (OnCompletedCallback != null)
                    {
                        var onceCalledDelegate = new EventHandler((sender, e) => { OnCompletedCallback();  });
                        qi.Completed += onceCalledDelegate;

                    }
                }
            }

            if (isFinished) //If we item is attached we fire events on caller manually and outside of critical section (AVOID RACE CONDITION)
            {
                if (OnCompletedCallback != null) OnCompletedCallback();
                //if (OnChange != null) OnChange(qi, new QueuedItemChangedEventArgs(QueuedItemStatus.Finished));

            }

            return qi;

        }


        private IRemoteBuilderQueueItem RequestBuildItem(ITfsTeamProjectConfiguration project, ITimeFrame timeFrame, bool forceRecalculate)
        {
            if (!IsInitialized) Initialize();
            lock (this)
            {

                var wb = _WaitingBuilds.SingleOrDefault(a => a.Project == project && a.TimeFrame == timeFrame);
                if (wb != null)
                {
                    if (!(wb.Status == QueuedItemStatus.Enqueued || wb.Status == QueuedItemStatus.Activated))
                    {
                        if (wb != _WaitingBuilds.Peek())
                        {
                            StackTrace st = new StackTrace();
                            Log.Error(null, "ERROR: RequestBuild has finished or new item in his _WaitingBuilds queue {0} {1}", wb.ToString(), st.ToString());
                        }
                    }

                    return wb;
                }
                else
                {
                    var fb = _FinishedBuilds.SingleOrDefault(a => a.Project == project && a.TimeFrame == timeFrame);
                    if (fb != null)
                    {
                        Debug.Assert(fb.Status == QueuedItemStatus.Finished, string.Format("ERROR: RequestBuild has unfinished item in his _FinishedBuilds queue: {0}", fb.ToString()));
                        if (forceRecalculate)
                        {
                            _FinishedBuilds.Remove(fb); //removed from finished
                            var nrb = new RemoteBuilderQueueItem(project, timeFrame, this, forceRecalculate);
                            RegisterInWaitingBuilds(nrb);
                            return nrb;
                        }
                        else
                        {
                            return fb;
                        }
                    }
                    else
                    {
                        var nrb = new RemoteBuilderQueueItem(project, timeFrame, this, forceRecalculate);
                        RegisterInWaitingBuilds(nrb);
                        return nrb;
                    }
                }

            }
        }

        private void RegisterInWaitingBuilds(RemoteBuilderQueueItem it)
        {
            if (!IsInitialized) Initialize();
            if (it.Owner != this) throw new InvalidOperationException(string.Format("You can move item {0} only from the owner {1}. An attempt was made to move the item in RemoteBuilderQueue {2}", it.ToString(), it.Owner.ToString(), this.ToString()));
            Log.Debug("RegisterING In Waiting Builds - {0}", it.ToString());
            lock (this)
            {
                it.Enqueued();
                _WaitingBuilds.Enqueue(it);
            }
            IsCalculating_Recalculate();
            WaitingBuildsCount_Recalculate();
            FinishedBuildsCount_Recalculate();
            Log.Debug("QueueThreadHasWork.Set - After enqueueing {0}", it.ToString());
            QueueThreadHasWork.Set();
        }

        private int Dequeue_Internal(RemoteBuilderQueueItem buildingItem)
        {
            lock (this)
            {

                var item = _WaitingBuilds.Dequeue();
                Debug.Assert(item == buildingItem, "Internal error on dequeueing item {0}. It should be equal to statItem {1}", item.ToString(), buildingItem.ToString());
                _FinishedBuilds.Add(item);
            }
            WaitingBuildsCount_Recalculate_NonUICall();
            FinishedBuildsCount_Recalculate_NonUICall();
            int cnt = 0;
            lock (this)
            {
                cnt = _WaitingBuilds.Count();
            }
            if (cnt == 0) QueueThreadHasWork.Reset();
            return cnt;


        }

        protected override void OnDisposing()
        {
            FinishQueueThread = true;
            if (QueueThread != null && QueueThreadHasWork != null)
            {
                FinishQueueThread = true;
                QueueThreadHasWork.Set();

                for (int i = 0; i < 5; i++)
                {
                    FinishQueueThread = true;
                    QueueThreadHasWork.Set();
                    if (QueueThread.IsAlive)
                    {
                        Log.TraceMethod("{0} in Dispose: Needs to wait for QueueThread [retry {1}]", this.ToString(), i);
                        Thread.Sleep(200);
                    }
                }
                try
                {
                    Log.Warn("{0} In Dispose: Timeout for QueueThread to finish normally reached. Requesting abort.", this.ToString());
                    QueueThread.Abort();
                }
                catch (Exception)
                {


                }


                QueueThreadHasWork.Dispose();
                QueueThreadHasWork = null;
                QueueThread = null;
            }
            else
            {
                if (QueueThreadHasWork != null)
                {
                    QueueThreadHasWork.Set();
                }
                Log.Warn("{0} is in incoherent state. Aborting QueueThread if any and disposing QueueThreadHasWork");
                if (QueueThread != null)
                {
                    try
                    {
                        QueueThread.Abort();
                    }
                    catch (Exception)
                    {

                    }

                    QueueThread = null;
                }
                if (QueueThreadHasWork != null)
                {
                    QueueThreadHasWork.Dispose();
                    QueueThreadHasWork = null;
                }
            }

            // No thread safety here. Thread is either already finished or we destroy both lists and if background thread is still running it will be terminated abnormally
            if (_WaitingBuilds != null) { _WaitingBuilds.Clear(); _WaitingBuilds = null; }
            if (_FinishedBuilds != null) { _FinishedBuilds.Clear(); _FinishedBuilds = null; }
        }

        public override string ToString()
        {
            if (!IsInitialized) Initialize();
            return string.Format("{0}[{1}, WB={2}, FB={3}]", this.GetType().Name, this.GetHashCode(), this._WaitingBuilds.Count, this._FinishedBuilds.Count);
        }

        //private static RemoteBuilderQueue _Default;
        //public static RemoteBuilderQueue Default
        //{
        //    get
        //    {
        //        if (_Default == null)
        //        {
        //            _Default = new RemoteBuilderQueue();
        //            _Default.Initialize();
        //        }
        //        return _Default;
        //    }
        //}

        internal static IRemoteBuilderQueueItem RequestAndWaitForRemoteBuild(ITfsTeamProjectConfiguration TeamProject, ITimeFrame timeFrame, bool forceRecalculate = false)
        {
            var Default = IoC.Get<IRemoteBuilderQueue>();
            if (Default == null) return null;
            try
            {
                return Default.GetBuild(TeamProject, timeFrame, forceRecalculate);
            }
            catch (Exception exc)
            {
                var Log = LogManager.GetLog(typeof(RemoteBuilderQueue));
                Log.Error(exc, "Request for bd for {0} - {1} failed. Details follows.", TeamProject.Label, timeFrame.Label);
                return null;
            }

        }





        public void TerminateCurrentBuild()
        {

            TerminateCurrentBuild_Requested = true;
        }


        public void TerminateAllBuilds()
        {
            TerminateCurrentBuild_Requested = true;
            FinishQueueThread = true;
        }
    }


}